package com.fy.opengltest.renderSphere;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

/**
 * Created by yao.fu on 17-2-7.
 */

public class Sphere {
    private final static float PI = 3.1415926535f; //FIXME: there is pi in math?
    private final static float PI_2 = 1.57079632679f;

    private FloatBuffer mVerticesBuffer;
    private FloatBuffer mTexCoordinateBuffer;
    private ShortBuffer indexBuffer;
    private int mNumIndices;


    /**
     * modified from hzqiujiadi on 16/1/8.
     * original source code:
     * https://github.com/shulja/viredero/blob/a7d28b21d762e8479dc10cde1aa88054497ff649/viredroid/src/main/java/org/viredero/viredroid/Sphere.java
     *
     * @param radius  半径，半径应该在远平面和近平面之间
     * @param sectors
     */

    public Sphere(float offset_x, float offset_y, float radius_cal,
                  float radius, int sectors, float degreeY, boolean isUpper) {
        generate(params);
    }

    private void generate(float offset_x, float offset_y, float radius_cal,
                          float radius, int sectors, float degreeY, boolean isUpper) {
        float percent = degreeY / 360;
        int rings = sectors >> 1;

        float R_Offset = radius_cal / rings;

        float R = 1f / rings;
        float S = 1f / sectors;
        short r, s;
        float x, y, z;

        int lenRings = (int) (rings * percent) + 1;
        int lenSectors = sectors + 1;
        int numPoint = lenRings * lenSectors;

        float[] vertexs = new float[numPoint * 3];
        float[] texcoords = new float[numPoint * 2];
        short[] indices = new short[numPoint * 6];

        int upper = isUpper ? 1 : -1;

        int t = 0, v = 0;
        for (r = 0; r < lenRings; r++) {
            for (s = 0; s < lenSectors; s++) {
                x = (float) (Math.cos(2 * PI * s * S) * Math.sin(PI * r * R)) * upper;
                y = (float) Math.sin(-PI_2 + PI * r * R) * -upper;
                z = (float) (Math.sin(2 * PI * s * S) * Math.sin(PI * r * R));

//                float a = (float) (Math.cos( 2 * PI * s * S) * r * R / percent)/2.0f + 0.5f;
//                float b = (float) (Math.sin( 2 * PI * s * S) * r * R / percent)/2.0f + 0.5f;

                float a = (float) (Math.cos(2 * PI * s * S) * r * R_Offset / percent) / 2.0f + 0.5f + offset_x;
                float b = (float) (Math.sin(2 * PI * s * S) * r * R_Offset / percent) / 2.0f + 0.5f + offset_y;

                texcoords[t++] = b;
                texcoords[t++] = a;

                vertexs[v++] = x * radius;
                vertexs[v++] = y * radius;
                vertexs[v++] = z * radius;
            }
        }

        int counter = 0;
        for (r = 0; r < lenRings - 1; r++) {
            for (s = 0; s < lenSectors - 1; s++) {
                indices[counter++] = (short) (r * lenSectors + s);       //(a)
                indices[counter++] = (short) ((r + 1) * lenSectors + (s));    //(b)
                indices[counter++] = (short) ((r) * lenSectors + (s + 1));  // (c)
                indices[counter++] = (short) ((r) * lenSectors + (s + 1));  // (c)
                indices[counter++] = (short) ((r + 1) * lenSectors + (s));    //(b)
                indices[counter++] = (short) ((r + 1) * lenSectors + (s + 1));  // (d)
            }
        }

        setmVerticesBuffer(vertexs);
        setmTexCoordinateBuffer(texcoords);
        setIndexBuffer(indices);
        setmNumIndices(indices.length);
    }

    public synchronized void setmVerticesBuffer(float[] vertexs) {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 4 bytes per float)
                vertexs.length * 4);
        bb.order(ByteOrder.nativeOrder());
        mVerticesBuffer = bb.asFloatBuffer();
        mVerticesBuffer.put(vertexs);
        mVerticesBuffer.position(0);
    }

    public synchronized void setmTexCoordinateBuffer(float[] texcoords) {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer cc = ByteBuffer.allocateDirect(
                texcoords.length * 4);
        cc.order(ByteOrder.nativeOrder());
        mTexCoordinateBuffer = cc.asFloatBuffer();
        mTexCoordinateBuffer.put(texcoords);
        mTexCoordinateBuffer.position(0);
    }

    public synchronized void setIndexBuffer(short[] indices) {
        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                indices.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        indexBuffer = dlb.asShortBuffer();
        indexBuffer.put(indices);
        indexBuffer.position(0);
    }

    public synchronized void setmNumIndices(int mNumIndices) {
        this.mNumIndices = mNumIndices;
    }

    public synchronized int getmNumIndices() {
        return mNumIndices;
    }

    public synchronized ShortBuffer getIndexBuffer() {
        return indexBuffer;
    }

    public synchronized FloatBuffer getVerticesBuffer() {
        return mVerticesBuffer;
    }

    public synchronized FloatBuffer getTexCoordinateBuffer() {
        return mTexCoordinateBuffer;
    }


    private HandlerThread workThread;
    private WorkHandler mWorkHandler;

    private void initWorkThread() {
        if (null == workThread) {
            workThread = new HandlerThread("work thread");
            workThread.start();
            mWorkHandler = new WorkHandler(workThread.getLooper());
        }
    }

    public boolean update(int type, float value) {
        switch (type) {
            case 0:
                params.setOffset_x(value);
                break;
            case 1:
                params.setOffset_y(value);
                break;
            case 2:
                params.setRadius_cal(value);
                break;
            case 3:
                params.setRadius(value);
                break;
            case 4:
                params.setDegreeY(value);
                break;
            default:
                return false;
        }
        update();
        return true;
    }

    private void update() {
        initWorkThread();
        mWorkHandler.sendMessage(mWorkHandler.obtainMessage(0, params));
    }

    private Params params = new Params();

    public String getParams() {
        return params.toString();
    }

    public float getOffsetX() {
        return params.getOffset_x();
    }

    public float getOffsetY() {
        return params.getOffset_y();
    }

    public float getRadiusCal() {
        return params.getRadius_cal();
    }

    public float getRadius() {
        return params.getRadius();
    }

    public float getDegree() {
        return params.getDegreeY();
    }

    private class Params {
        float offset_x = 0f;
        float offset_y = 0f;
        float radius_cal = 1f;
        float radius = 18f;
        int sectors = 180;

        float degreeY = 180f;
        boolean isUpper = true;

        public Params(float offset_x, float offset_y, float radius_cal) {
            this.offset_x = offset_x;
            this.offset_y = offset_y;
            this.radius_cal = radius_cal;
        }

        @Override
        public String toString() {
            return "Params{" +
                    "offset_x=" + offset_x +
                    ", offset_y=" + offset_y +
                    ", radius_cal=" + radius_cal +
                    ", radius=" + radius +
                    ", sectors=" + sectors +
                    ", degreeY=" + degreeY +
                    ", isUpper=" + isUpper +
                    '}';
        }

        public Params() {
        }

        public float getDegreeY() {
            return degreeY;
        }

        public void setDegreeY(float degreeY) {
            this.degreeY = degreeY;
        }

        public void setOffset_x(float offset_x) {
            this.offset_x = offset_x;
        }

        public void setOffset_y(float offset_y) {
            this.offset_y = offset_y;
        }

        public void setRadius_cal(float radius_cal) {
            this.radius_cal = radius_cal;
        }

        public void setRadius(float radius) {
            this.radius = radius;
        }

        public float getOffset_x() {
            return offset_x;
        }

        public float getOffset_y() {
            return offset_y;
        }

        public float getRadius_cal() {
            return radius_cal;
        }

        public float getRadius() {
            return radius;
        }
    }

    private class WorkHandler extends Handler {
        public WorkHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            generate(params);
        }
    }

    private void generate(Params params) {
        generate(params.offset_x, params.offset_y, params.radius_cal,
                params.radius, params.sectors, params.degreeY, params.isUpper);
    }
}
